home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume22 / nn6.4 / part10 < prev    next >
Encoding:
Internet Message Format  |  1990-06-07  |  53.9 KB

  1. Subject:  v22i045:  NN Newsreader, release 6.4, Part10/21
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4. X-Checksum-Snefru: e60d6ea7 a14e823a 18b99a38 34670250
  5.  
  6. Submitted-by: "Kim F. Storm" <storm@texas.dk>
  7. Posting-number: Volume 22, Issue 45
  8. Archive-name: nn6.4/part10
  9.  
  10. #! /bin/sh
  11. # This is a shell archive.  Remove anything before this line, then feed it
  12. # into a shell via "sh file" or similar.  To overwrite existing files,
  13. # type "sh file -c".
  14. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  15. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  16. # Contents:  aux.sh group.c nntp.c
  17. # Wrapped by storm@texas.dk on Sun May  6 18:19:43 1990
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. echo If this archive is complete, you will see the following message:
  20. echo '          "shar: End of archive 10 (of 22)."'
  21. if test -f 'aux.sh' -a "${1}" != "-c" ; then 
  22.   echo shar: Will not clobber existing file \"'aux.sh'\"
  23. else
  24.   echo shar: Extracting \"'aux.sh'\" \(5571 characters\)
  25.   sed "s/^X//" >'aux.sh' <<'END_OF_FILE'
  26. X
  27. X# PREFIX is inserted above this line during Make
  28. X
  29. Xtrap : 2 3
  30. X
  31. XPATH=/bin:$PATH
  32. Xexport PATH
  33. X
  34. X# Paramaters transferred from nn via .param file:
  35. X#    ART_ID        Article id to cancel
  36. X#    GROUP        Group of article to cancel
  37. X#    WORK        Temporary file for response (w initial contents)
  38. X#    FIRST_ACTION    First action to perform
  39. X#    EMPTY_CHECK    [empty-response-check]
  40. X#    EDITOR        [editor]
  41. X#    ED_LINE        First line of body in WORK file
  42. X#    SPELL_CHECKER    [spell-checker]
  43. X#    PAGER        [pager]
  44. X#    APPEND_SIG    [append-signature]
  45. X#    QUERY_SIG    [query-signature]
  46. X#    NOVICE        [expert]
  47. X#    WAIT_PERIOD     [response-check-pause]
  48. X#    RECORD        [mail/news-record]
  49. X#    MAILER        [mailer]
  50. X#    MAILER_PIPE    [mailer-pipe-input]
  51. X#    DFLT_ANSW    [response-default-answer]
  52. X
  53. X. ${HOME}/.nn/.param
  54. X
  55. X# first argument is operation to be performed:
  56. X
  57. XOPERATION=$1
  58. X
  59. X# first we handle 'cancel'
  60. X
  61. Xif [ "$OPERATION" = "cancel" ] ; then
  62. X
  63. X  $INEWS -h  << EOF > /tmp/nn$$c 2>&1
  64. XNewsgroups: $GROUP
  65. XSubject: cancel $ART_ID
  66. XControl: cancel $ART_ID
  67. X
  68. Xcancel $ART_ID in newsgroup $GROUP
  69. XEOF
  70. X
  71. X  x=$?
  72. X  case $x in
  73. X    0) ;;
  74. X    *) echo '' ; cat /tmp/nn$$c ; sleep 2 ;;
  75. X  esac
  76. X  rm -f /tmp/nn$$c
  77. X  exit $x
  78. Xfi
  79. X
  80. X
  81. XTRACE=${WORK}T
  82. XFINAL=${WORK}F
  83. XCOPY=""
  84. X
  85. Xif [ "${FIRST_ACTION}" != "send" ] ; then
  86. X  COPY=${WORK}C
  87. X  cp $WORK $COPY
  88. Xfi
  89. X
  90. X# loop until sent or aborted.
  91. X
  92. Xloop=true
  93. Xprompt=false
  94. Xpr="Action: a)bort e)dit"
  95. Xif [ -n "$(SPELL_CHECKER)" ] ; then
  96. X  pr="$pr i)spell"
  97. Xfi
  98. Xpr="$pr r)eedit s)end v)iew w)rite: "
  99. Xif [ -n "${DFLT_ANSW}" ] ; then
  100. X  pr="$pr (${DFLT_ANSW}) "
  101. Xfi
  102. X
  103. Xwhile $loop ; do
  104. X  if [ -n "${FIRST_ACTION}" ] ; then
  105. X    act="${FIRST_ACTION}"
  106. X    FIRST_ACTION=""
  107. X  elif $prompt ; then
  108. X    echo ''
  109. X    ${AWK} 'END{printf "'"${pr}"'"}' < /dev/null
  110. X    read act
  111. X    if [ -z "$act" ] ; then
  112. X      act="${DFLT_ANSW}"
  113. X    fi
  114. X  fi
  115. X  prompt=true
  116. X
  117. X  case $act in
  118. X  a*)
  119. X    ${AWK} 'END{printf "Confirm abort: (y) "}' < /dev/null
  120. X    read act
  121. X    case "$act" in
  122. X    ""|"y*") rm -f $WORK $COPY ;
  123. X         exit 22 ;;
  124. X    esac
  125. X    ;;
  126. X  e*)
  127. X    # call editor to enter at line $ED_LINE of work file
  128. X
  129. X    case `basename "${EDITOR-vi}"` in
  130. X    vi|emacs|emacsclient )
  131. X      # Berkeley vi display editor
  132. X      # GNU emacs disply editor
  133. X      ${EDITOR-vi} +${ED_LINE} $WORK
  134. X      ;;
  135. X    ded )
  136. X      # QMC ded display editor
  137. X      $EDITOR -l${ED_LINE} $WORK
  138. X      ;;
  139. X    uemacs )
  140. X      # micro emacs
  141. X      $EDITOR -g${ED_LINE} $WORK
  142. X      ;;
  143. X    * )
  144. X      # Unknown editor
  145. X      $EDITOR $WORK
  146. X      ;;
  147. X    esac
  148. X
  149. X    if [ ! -s $WORK ] ; then
  150. X      rm -f $WORK $COPY
  151. X      exit 22
  152. X    fi
  153. X
  154. X    if ${EMPTY_CHECK} ; then
  155. X      if cmp -s $WORK $COPY ; then
  156. X        rm -f $WORK $COPY
  157. X        exit 22
  158. X      fi
  159. X    fi
  160. X    ;;
  161. X
  162. X  i*)
  163. X    if [ -n "${SPELL_CHECKER}" ] ; then
  164. X      ${SPELL_CHECKER} ${WORK}
  165. X    else
  166. X      echo "spell-checker not defined"
  167. X    fi
  168. X    ;;
  169. X
  170. X  r*)
  171. X    ${AWK} 'END{printf "Edit original file: (y) "}' < /dev/null
  172. X    read act
  173. X    case "$act" in
  174. X    ""|"y*") cp $COPY $WORK ;;
  175. X    esac
  176. X    FIRST_ACTION=edit
  177. X    ;;
  178. X
  179. X  s*)
  180. X    loop=false
  181. X    ;;
  182. X
  183. X  v*)
  184. X    ${PAGER-cat} $WORK
  185. X    ;;
  186. X
  187. X  w*)
  188. X    echo "Append article to file:"
  189. X    read FNAME
  190. X    if [ -n "$FNAME" ] ; then
  191. X      { cat $WORK ; echo ; } >> $FNAME
  192. X    fi
  193. X    ;;
  194. X  esac
  195. Xdone
  196. X
  197. Xcase "$OPERATION" in
  198. X
  199. X  reply|forward|mail)
  200. X    if [ ${APPEND_SIG} = "true" -a -f $HOME/.signature ] ; then
  201. X      if ${QUERY_SIG} ; then
  202. X          ${AWK} 'END{printf "Append .signature? (y) "}' < /dev/null
  203. X          read ans
  204. X      else
  205. X          ans=y
  206. X      fi
  207. X      case $ans in
  208. X      ''|y*|Y*)
  209. X        echo "--" >> $WORK
  210. X        cat $HOME/.signature >> $WORK
  211. X        ;;
  212. X      esac
  213. X    fi
  214. X    ;;
  215. X  follow|post)
  216. X    if ${NOVICE} ; then
  217. X      echo "Be patient! Your new article will not show up immediately."
  218. X      case ${WAIT_PERIOD-0} in
  219. X    0|1) WAIT_PERIOD=2 ;;
  220. X      esac
  221. X    fi
  222. X    ;;
  223. Xesac
  224. X
  225. X{
  226. X  trap 'echo SIGNAL' 1 2 3
  227. X
  228. X  grep -v "^Orig-To: " $WORK > $FINAL
  229. X
  230. X  LOGNAME="${LOGNAME-$USER}"
  231. X  if [ -z "${LOGNAME}" ] ; then
  232. X    set `who am i`
  233. X    LOGNAME="$1"
  234. X  fi
  235. X
  236. X  if [ -n "${RECORD}" ] ; then
  237. X  {
  238. X    # keep a copy of message in $RECORD (in mail format)
  239. X    set `date`
  240. X    if [ $3 -gt 9 ] ; then
  241. X      echo From ${LOGNAME} $1 $2 $3 $4 $6 $7
  242. X    else
  243. X      echo From ${LOGNAME} $1 $2 " $3" $4 $6 $7
  244. X    fi
  245. X    echo "From: ${LOGNAME}"
  246. X    cat $FINAL
  247. X    echo ''
  248. X  } >> "$RECORD"
  249. X  fi
  250. X
  251. X  case "$OPERATION" in
  252. X
  253. X    reply|forward|mail)
  254. X      if ${MAILER_PIPE} ; then
  255. X        $MAILER < $FINAL
  256. X        x=$?
  257. X      else
  258. X        $MAILER $FINAL
  259. X        x=$?
  260. X      fi
  261. X      case $x in
  262. X    0) ;;
  263. X    *) echo $MAILER failed ;;
  264. X      esac
  265. X      ;;
  266. X
  267. X    follow|post)
  268. X      $INEWS -h < $FINAL
  269. X      case $? in
  270. X    0) sleep 60 ;;
  271. X    *) echo $INEWS failed ;;
  272. X      esac
  273. X      ;;
  274. X
  275. X    *)
  276. X      echo "Invalid operation: $OPERATION -- help"
  277. X      OPERATION="nn response operation"
  278. X      ;;
  279. X  esac > $TRACE 2>&1
  280. X
  281. X  if [ -s $TRACE ] ; then
  282. X    if [ -s $HOME/dead.letter ] ; then
  283. X      cat $HOME/dead.letter >> $HOME/dead.letters
  284. X      echo '' >> $HOME/dead.letters
  285. X    fi
  286. X    cat $WORK > $HOME/dead.letter
  287. X
  288. X    # Gripe: Error-report is lost if REC_MAIL was the problem
  289. X    {
  290. X      echo "To: ${LOGNAME}"
  291. X      echo "Subject: $OPERATION failed"
  292. X      echo ''
  293. X      cat $TRACE
  294. X      echo ''
  295. X      echo 'Your response has been saved in ~/dead.letter'
  296. X      echo ''
  297. X      echo 'Your article/letter follows:'
  298. X      cat $WORK
  299. X    } > $FINAL
  300. X    if ${MAILER_PIPE} ; then
  301. X      $MAILER < $FINAL
  302. X    else
  303. X      $MAILER $FINAL
  304. X    fi
  305. X  else
  306. X    # keep TRACE file a little while for test at end of script
  307. X    sleep 3
  308. X  fi
  309. X  rm -f $WORK $COPY $TRACE $FINAL
  310. X
  311. X} > /dev/null 2>&1 &
  312. X
  313. Xcase ${WAIT_PERIOD-0} in
  314. X  0) ;;
  315. X  *) sleep ${WAIT_PERIOD} ;;
  316. Xesac
  317. X
  318. Xif [ -s "$TRACE" ] ; then
  319. X  exit 1
  320. Xfi
  321. X
  322. Xexit 0
  323. END_OF_FILE
  324.   if test 5571 -ne `wc -c <'aux.sh'`; then
  325.     echo shar: \"'aux.sh'\" unpacked with wrong size!
  326.   fi
  327.   # end of 'aux.sh'
  328. fi
  329. if test -f 'group.c' -a "${1}" != "-c" ; then 
  330.   echo shar: Will not clobber existing file \"'group.c'\"
  331. else
  332.   echo shar: Extracting \"'group.c'\" \(22393 characters\)
  333.   sed "s/^X//" >'group.c' <<'END_OF_FILE'
  334. X/*
  335. X *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  336. X *
  337. X *    News group access.
  338. X */
  339. X
  340. X#include "config.h"
  341. X#include "articles.h"
  342. X#include "db.h"
  343. X#include "term.h"
  344. X#include "menu.h"
  345. X#include "keymap.h"
  346. X#ifdef HAVE_SYSLOG
  347. X#include <syslog.h>
  348. X#endif
  349. X
  350. Xexport int  dont_split_digests = 0;
  351. Xexport int  dont_sort_articles = 0;
  352. Xexport int  also_cross_postings = 0;
  353. Xexport int  also_unsub_groups = 0;
  354. Xexport int  entry_message_limit = 400;
  355. X
  356. Ximport int  article_limit, also_read_articles;
  357. Ximport int  no_update, novice, case_fold_search;
  358. Ximport int  merged_menu, keep_unsubscribed, keep_unsub_long;
  359. Ximport int  killed_articles;
  360. Ximport int  seq_cross_filtering;
  361. Ximport char *default_save_file;
  362. X
  363. Ximport char delayed_msg[];
  364. Ximport int32 db_read_counter;
  365. X
  366. X/*
  367. X * completion of group name
  368. X */
  369. X
  370. Xgroup_completion(hbuf, ix)
  371. Xchar *hbuf;
  372. Xint ix;
  373. X{
  374. X    static group_number next_group, n1, n2;
  375. X    static char *head, *tail, *last;
  376. X    static int  tail_offset, prev_lgt, l1, l2;
  377. X    static group_header *prev_group, *p1, *p2;
  378. X    register group_header *gh;
  379. X    register char *t1, *t2;
  380. X    int order;
  381. X
  382. X    if (ix < 0) return 0;
  383. X
  384. X    if (hbuf) {
  385. X    n2 = next_group = 0;
  386. X    p2 = prev_group = NULL;
  387. X    l2 = 0;
  388. X
  389. X    if (head = strrchr(hbuf, ','))
  390. X        head++;
  391. X    else
  392. X        head = hbuf;
  393. X    tail = hbuf + ix;
  394. X    tail_offset = ix - (head - hbuf);
  395. X    if (last = strrchr(head, '.')) last++; else last = head;
  396. X    return 1;
  397. X    }
  398. X
  399. X    if (ix) {
  400. X    n1 = next_group, p1 = prev_group, l1 = prev_lgt;
  401. X    next_group = n2, prev_group = p2, prev_lgt = l2;
  402. X    list_completion((char *)NULL);
  403. X    }
  404. X
  405. X    *tail = NUL;
  406. X
  407. X    while (next_group < master.number_of_groups) {
  408. X    gh = sorted_groups[next_group++];
  409. X    if (gh->master_flag & M_IGNORE_GROUP) continue;
  410. X    if (gh->group_name_length <= tail_offset) continue;
  411. X
  412. X    if (prev_group &&
  413. X        strncmp(prev_group->group_name, gh->group_name, prev_lgt) == 0)
  414. X        continue;
  415. X
  416. X    order = strncmp(gh->group_name, head, tail_offset);
  417. X    if (order < 0) continue;
  418. X    if (order > 0) break;
  419. X
  420. X    t1 = gh->group_name + tail_offset;
  421. X    if (t2 = strchr(t1, '.')) {
  422. X        strncpy(tail, t1, t2 - t1 + 1);
  423. X        tail[t2 - t1 + 1] = NUL;
  424. X    } else
  425. X        strcpy(tail, t1);
  426. X
  427. X    prev_group = gh;
  428. X    prev_lgt = tail_offset + strlen(tail);
  429. X    if (ix) {
  430. X        if (list_completion(last) == 0) break;
  431. X    } else
  432. X        return 1;
  433. X    }
  434. X
  435. X    if (ix) {
  436. X    n2 = next_group, p2 = prev_group, l2 = prev_lgt;
  437. X    if (n2 > master.number_of_groups)
  438. X        n2 = 0, p2 = NULL, l2 = 0;
  439. X    next_group = n1, prev_group = p1, prev_lgt = l1;
  440. X    return 1;
  441. X    }
  442. X
  443. X    next_group = 0;
  444. X    prev_group = NULL;
  445. X    return 0;
  446. X}
  447. X
  448. Xstatic int group_level = 0;
  449. X
  450. Xstatic print_header()
  451. X{
  452. X    extern long unread_articles;
  453. X    extern int unread_groups;
  454. X
  455. X    so_printxy(0, 0, "Newsgroup: %s", current_group->group_name);
  456. X
  457. X    if (current_group->group_flag & G_MERGED) printf("    MERGED");
  458. X
  459. X    clrline();
  460. X
  461. X    so_gotoxy(-1, 0, 0);
  462. X
  463. X    so_printf("Articles: %d", n_articles);
  464. X
  465. X    if (no_update) {
  466. X    so_printf(" NO UPDATE");
  467. X    } else {
  468. X    if (current_group->current_first > current_group->first_article + 1)
  469. X        so_printf((killed_articles > 0) ? "(L-%ld,K-%d)" : "(L-%ld)",
  470. X              current_group->current_first - current_group->first_article - 1,
  471. X              killed_articles);
  472. X    else
  473. X        if (killed_articles > 0)
  474. X        so_printf(" (K-%d)", killed_articles);
  475. X
  476. X    if (unread_articles > 0) {
  477. X        so_printf(" of %ld", unread_articles);
  478. X        if (unread_groups > 0)
  479. X        so_printf("/%d", unread_groups);
  480. X    }
  481. X
  482. X    if (current_group->group_flag & G_UNSUBSCRIBED)
  483. X        so_printf(" UNSUB");
  484. X    else if (current_group->group_flag & G_NEW)
  485. X        so_printf(" NEW");
  486. X
  487. X    if (current_group->unread_count <= 0)
  488. X        so_printf(" READ");
  489. X
  490. X    if (group_level > 1)
  491. X        so_printf(" *NO*UPDATE*");
  492. X    }
  493. X
  494. X    so_end();
  495. X
  496. X    return 1;    /* number of lines in header */
  497. X}
  498. X
  499. X/*
  500. X * interpretation of first_art:
  501. X *    >0    Read articles first_art..last_db_article (also read)
  502. X *    -1    Read unread or read articles accoring to -x and -aN flags
  503. X */
  504. X
  505. Xgroup_menu(gh, first_art, access_mode, mask, menu)
  506. Xregister group_header *gh;
  507. Xarticle_number first_art;
  508. Xregister flag_type access_mode;
  509. Xchar *mask;
  510. Xfct_type menu;
  511. X{
  512. X    int status, was_unread, unread_at_reentry = 0;
  513. X    int menu_cmd, o_killed;
  514. X    article_number prev_last, n;
  515. X    register group_header *mg_head;
  516. X
  517. X#define    menu_return(cmd) { menu_cmd = (cmd); goto menu_exit; }
  518. X
  519. X    o_killed = killed_articles;
  520. X    mark_var_stack();
  521. X
  522. X    mg_head = (group_level == 0 && gh->group_flag & G_MERGE_HEAD) ? gh : NULL;
  523. X    group_level++;
  524. X
  525. X reenter:
  526. X    for (; gh; gh = gh->merge_with) {
  527. X    if (init_group(gh) <= 0) {
  528. X        if (mg_head != NULL) continue;
  529. X        menu_return( ME_NEXT );
  530. X    }
  531. X
  532. X    if (unread_at_reentry) restore_unread(gh);
  533. X
  534. X    m_invoke(-1);
  535. X
  536. X    killed_articles = 0;
  537. X
  538. X    /* don't lose (a few) new articles when reentering a read group */
  539. X    /* (the user will expect the menu to be the same, and may overlook */
  540. X    /* the new articles) */
  541. X    if (gh->group_flag & G_READ) goto update_unsafe;
  542. X
  543. X#ifdef RENUMBER_DANGER
  544. X    prev_last = gh->last_db_article;
  545. X#endif
  546. X    was_unread = add_unread(gh, -1);
  547. X
  548. X    switch (update_group(gh)) {
  549. X     case -1:
  550. X        status = -1;
  551. X        goto access_exception;
  552. X     case -3:
  553. X        return ME_QUIT;
  554. X    }
  555. X
  556. X#ifdef RENUMBER_DANGER
  557. X    if (gh->last_db_article < prev_last) {
  558. X        /* expire + renumbering */
  559. X        flag_type updflag;
  560. X
  561. X        if ((gh->last_article = gh->first_db_article - 1) < 0)
  562. X        gh->last_article = 0;
  563. X        gh->first_article = gh->last_article;
  564. X        updflag = gh->group_flag & G_READ;
  565. X        gh->group_flag &= ~G_READ;
  566. X        update_rc_all(gh, 0);
  567. X        gh->group_flag &= ~G_READ;
  568. X        gh->group_flag |= updflag;
  569. X    }
  570. X#endif
  571. X
  572. X    if (was_unread)
  573. X        add_unread(gh, 1);
  574. X
  575. X     update_unsafe:
  576. X    if ((gh->current_first = first_art) < 0) {
  577. X        if (access_mode & ACC_ORIG_NEWSRC)
  578. X        gh->current_first = gh->first_article + 1;
  579. X        else if (access_mode & ACC_ALSO_READ_ARTICLES)
  580. X        gh->current_first = gh->first_db_article;
  581. X        else
  582. X        gh->current_first = gh->last_article + 1;
  583. X    }
  584. X    
  585. X    if (gh->current_first <= 0) gh->current_first = 1;
  586. X    
  587. X    if (article_limit > 0) {
  588. X        n = gh->last_db_article - article_limit + 1;
  589. X        if (gh->current_first < n) gh->current_first = n;
  590. X    }
  591. X
  592. X    if (mg_head != NULL) {
  593. X        access_mode |= ACC_MERGED_MENU | ACC_DONT_SORT_ARTICLES;
  594. X        if (mg_head != gh) access_mode |= ACC_EXTRA_ARTICLES;
  595. X    }
  596. X
  597. X    if (entry_message_limit &&
  598. X        (n = gh->last_db_article - gh->current_first) >= entry_message_limit) {
  599. X        clrdisp();
  600. X        printf("Reading %s: %ld articles...", gh->group_name, (long)n);
  601. X    } else
  602. X        home();
  603. X    fl;
  604. X
  605. X    status = access_group(gh, gh->current_first, gh->last_db_article,
  606. X                  access_mode, mask);
  607. X
  608. X     access_exception:
  609. X
  610. X    if (status < 0) {
  611. X        if (status == -2) {
  612. X        clrdisp();
  613. X        printf("Read error on group %s (NFS timeout?)\n\r",
  614. X               current_group->group_name);
  615. X        printf("Skipping to next group....  (interrupt to quit)\n\r");
  616. X        if (any_key(0) == K_interrupt) nn_exit(0);
  617. X        }
  618. X
  619. X        if (status <= -10) {
  620. X        clrdisp();
  621. X        msg("DATABASE CORRUPTED FOR GROUP %s", gh->group_name);
  622. X#ifdef HAVE_SYSLOG
  623. X        openlog("nn", LOG_CONS, LOG_DAEMON);
  624. X        syslog(LOG_ALERT, "database corrupted for newsgroup %s.",
  625. X               gh->group_name);
  626. X        closelog();
  627. X#endif
  628. X        user_delay(5);
  629. X        }
  630. X        if (mg_head != NULL) continue;
  631. X        menu_return( ME_NEXT );
  632. X    }
  633. X
  634. X    if (mg_head == NULL) break;
  635. X    }
  636. X
  637. X    if (n_articles == 0) {
  638. X    m_break_entry();
  639. X    menu_return( ME_NO_ARTICLES );
  640. X    }
  641. X
  642. X    if (mg_head != NULL) {
  643. X    if (!dont_sort_articles) sort_articles(-1);
  644. X    init_group(mg_head);
  645. X    }
  646. X
  647. X samemenu:
  648. X    menu_cmd = CALL(menu)(print_header);
  649. X
  650. X    if (menu_cmd == ME_REENTER_GROUP) {
  651. X    if (group_level != 1) {
  652. X        strcpy(delayed_msg, "can only unread groups at topmost level");
  653. X        goto samemenu;
  654. X    }
  655. X    free_memory();
  656. X    if (mg_head) gh = mg_head;
  657. X    unread_at_reentry = 1;
  658. X    goto reenter;
  659. X    }
  660. X
  661. X menu_exit:
  662. X
  663. X    n = 1;            /* must unsort articles */
  664. X    if (mg_head != NULL) gh = mg_head;
  665. X
  666. X    if (!no_update) {
  667. X    do {
  668. X        if (mask == NULL && (access_mode & ACC_ALSO_READ_ARTICLES) == 0
  669. X        && first_art < 0) {
  670. X        if (menu_cmd != ME_NO_ARTICLES) gh->group_flag &= ~G_NEW;
  671. X        if ((gh->group_flag & G_UNSUBSCRIBED) == 0 || keep_unsub_long) {
  672. X            if (n) sort_articles(0);
  673. X            n = 0;
  674. X            update_rc(gh);
  675. X        }
  676. X        }
  677. X        gh->current_first = 0;
  678. X    } while (mg_head != NULL && (gh = gh->merge_with) != NULL);
  679. X    }
  680. X
  681. X    killed_articles = o_killed;
  682. X    restore_variables();
  683. X    group_level--;
  684. X
  685. X    return menu_cmd;
  686. X}
  687. X
  688. X
  689. Xgoto_group(command, ah, access_mode)
  690. Xint command;
  691. Xarticle_header *ah;
  692. Xflag_type access_mode;
  693. X{
  694. X    register group_header *gh;
  695. X    char ans1, *answer, *mask, buffer[FILENAME], fbuffer[FILENAME];
  696. X    article_number first;
  697. X    memory_marker mem_marker;
  698. X    group_header *orig_group;
  699. X    int menu_cmd;
  700. X    extern int menu(), file_completion();
  701. X    extern article_header *get_menu_article();
  702. X    extern int get_from_macro;
  703. X    extern group_header *jump_to_group;
  704. X
  705. X#define goto_return( cmd ) \
  706. X    { menu_cmd = cmd; goto goto_exit; }
  707. X
  708. X    m_startinput();
  709. X
  710. X    gh = orig_group = current_group;
  711. X
  712. X    mask = NULL;
  713. X
  714. X    if (command == K_GOTO_GROUP)
  715. X    goto get_group_name;
  716. X
  717. X    if (command == K_ADVANCE_GROUP)
  718. X    gh = gh->next_group;
  719. X    else
  720. X    gh = gh->prev_group;
  721. X
  722. X    for (;;) {
  723. X    if (gh == NULL)
  724. X        goto_return(ME_NO_REDRAW);
  725. X
  726. X    if (gh->first_db_article < gh->last_db_article && gh->current_first <= 0) {
  727. X        sprintf(buffer, "%s%s%s) ",
  728. X           (gh->group_flag & G_UNSUBSCRIBED) ? " UNSUB" : "",
  729. X           (gh->group_flag & G_MERGE_HEAD) ? " MERGED" : "",
  730. X           gh->unread_count <= 0 ? " READ" : "" );
  731. X        buffer[0] = buffer[0] == ')' ? NUL : '(';
  732. X        
  733. X        prompt("\1Enter\1 %s %s?  (ABGNPy) ", gh->group_name, buffer);
  734. X        
  735. X        command = get_c();
  736. X        if (command & GETC_COMMAND) goto_return(ME_REDRAW);
  737. X        if (command == 'y') break;
  738. X        command = menu_key_map[command];
  739. X    }
  740. X
  741. X    switch (command) {
  742. X     case K_CONTINUE:
  743. X     case K_CONTINUE_NO_MARK:
  744. X        break;
  745. X
  746. X     case K_ADVANCE_GROUP:
  747. X        gh = gh->next_group;
  748. X        continue;
  749. X
  750. X     case K_BACK_GROUP:
  751. X        gh = gh->prev_group;
  752. X        continue;
  753. X
  754. X     case K_GOTO_GROUP:
  755. X        goto get_group_name;
  756. X
  757. X     case K_PREVIOUS:
  758. X        while (gh = gh->prev_group) {
  759. X        if (gh->group_flag & G_MERGE_SUB) continue;
  760. X        if (gh->group_flag & G_COUNTED) break;
  761. X        if (gh->newsrc_line != gh->newsrc_orig) break;
  762. X        }
  763. X        continue;
  764. X
  765. X     case K_NEXT_GROUP_NO_UPDATE:
  766. X        while (gh = gh->next_group) {
  767. X        if (gh->group_flag & G_MERGE_SUB) continue;
  768. X        if (gh->group_flag & G_COUNTED) break;
  769. X        if (gh->newsrc_line != gh->newsrc_orig) break;
  770. X        }
  771. X        continue;
  772. X
  773. X     default:
  774. X        goto_return(ME_NO_REDRAW);
  775. X    }
  776. X    break;
  777. X    }
  778. X
  779. X    if (gh == orig_group) goto_return(ME_NO_REDRAW);
  780. X
  781. X    goto get_first;
  782. X
  783. X get_group_name:
  784. X
  785. X    if (current_group == NULL) {
  786. X    prompt("\1Enter Group or Folder\1 (+./~) ");
  787. X    answer = get_s(NONE, NONE, "+./~", group_completion);
  788. X    } else {
  789. X    prompt("\1Group or Folder\1 (+./~ %%%s=sneN) ",
  790. X           (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "");
  791. X    strcpy(buffer, "++./0123456789~=% ");
  792. X    if (gh->master_flag & M_AUTO_ARCHIVE) buffer[0] = '@';
  793. X    answer = get_s(NONE, NONE, buffer, group_completion);
  794. X    }
  795. X
  796. X    if (answer == NULL) goto_return(ME_NO_REDRAW);
  797. X
  798. X    if ((ans1 = *answer) == NUL || ans1 == SP) {
  799. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  800. X    goto get_first;
  801. X    }
  802. X
  803. X    sprintf(buffer, "%c", ans1);
  804. X
  805. X    switch (ans1) {
  806. X
  807. X     case '@':
  808. X    if (current_group == NULL ||
  809. X        (current_group->master_flag & M_AUTO_ARCHIVE) == 0)
  810. X        goto_return(ME_NO_REDRAW);
  811. X    answer = current_group->archive_file;
  812. X    goto get_folder;
  813. X
  814. X     case '%':
  815. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  816. X    if ((current_group->group_flag & G_FOLDER) == 0) {
  817. X        if (!ah) {
  818. X        prompt("\1READ\1");
  819. X        if ((ah = get_menu_article()) == NULL)
  820. X            goto_return(ME_NO_REDRAW);
  821. X        }
  822. X        if ((ah->flag & A_DIGEST) == 0) {
  823. X        *group_file_name = NUL;
  824. X        sprintf(fbuffer, "%s%ld", group_path_name, ah->a_number);
  825. X        answer = fbuffer;
  826. X        goto get_folder;
  827. X        }
  828. X    }
  829. X
  830. X    msg("cannot split articles inside a folder or digest");
  831. X    goto_return(ME_NO_REDRAW);
  832. X
  833. X     case '.':
  834. X     case '~':
  835. X    strcat(buffer, "/");
  836. X     case '+':
  837. X     case '/':
  838. X    if (!get_from_macro) {
  839. X        prompt("\1Folder\1 ");
  840. X        answer = get_s(NONE, buffer, NONE, file_completion);
  841. X        if (answer == NULL || answer[0] == NUL) goto_return(ME_NO_REDRAW);
  842. X    }
  843. X    goto get_folder;
  844. X    
  845. X     case 'a':
  846. X    if (answer[1] != NUL && strcmp(answer, "all")) break;
  847. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  848. X    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  849. X    first = 0;
  850. X    goto more_articles;
  851. X
  852. X     case 'u':
  853. X    if (answer[1] != NUL && strcmp(answer, "unread")) break;
  854. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  855. X    access_mode |= ACC_ORIG_NEWSRC;
  856. X    first = gh->first_article + 1;
  857. X    goto enter_new_level;
  858. X
  859. X     case 'e':
  860. X     case 'n':
  861. X     case 's':
  862. X    if (answer[1] != NUL && answer[1] != '=') break;
  863. X    /* fall thru */
  864. X     case '=':
  865. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  866. X    goto get_mask;
  867. X
  868. X     case '0':
  869. X     case '1':
  870. X     case '2':
  871. X     case '3':
  872. X     case '4':
  873. X     case '5':
  874. X     case '6':
  875. X     case '7':
  876. X     case '8':
  877. X     case '9':
  878. X    if (current_group == NULL) goto_return(ME_NO_REDRAW);
  879. X    if (gh->current_first <= gh->first_db_article) {
  880. X        msg("No extra articles");
  881. X        flush_input();
  882. X        goto_return(ME_NO_REDRAW);
  883. X    }
  884. X    if (!get_from_macro) {
  885. X        prompt("\1Number of extra articles\1 max %ld: ",
  886. X           gh->current_first - gh->first_db_article);
  887. X        sprintf(buffer, "%c", ans1);
  888. X        answer = get_s(NONE, buffer, NONE, NULL_FCT);
  889. X        if (answer == NULL || *answer ==  NUL) goto_return(ME_NO_REDRAW);
  890. X    }
  891. X    first = gh->current_first - atol(answer);
  892. X    goto more_articles;
  893. X
  894. X     default:
  895. X    break;
  896. X    }
  897. X
  898. X    if ((gh = lookup(answer)) == NULL) {
  899. X    msg("No group named %s", answer);
  900. X    goto_return(ME_NO_REDRAW);
  901. X    }
  902. X
  903. X
  904. X get_first:
  905. X
  906. X    m_advinput();
  907. X
  908. X    if (gh->master_flag & M_BLOCKED ||
  909. X    gh->last_db_article == 0 ||
  910. X    gh->last_db_article < gh->first_db_article) {
  911. X    msg("Group %s is %s", gh->group_name,
  912. X        (gh->master_flag & M_BLOCKED) ? "blocked" : "empty");
  913. X    goto_return(ME_NO_REDRAW);
  914. X    }
  915. X
  916. X    if (gh != orig_group) {
  917. X    if (gh->current_first > 0) {
  918. X        msg("Group %s already active", gh->group_name);
  919. X        goto_return(ME_NO_REDRAW);
  920. X    }
  921. X    gh->current_first = gh->last_db_article + 1;
  922. X    }
  923. X
  924. X    ans1 = ah ? 's' : 'a';
  925. X    if (gh != orig_group) {
  926. X    if (gh->unread_count > 0) ans1 = 'j';
  927. X    } else {
  928. X    if (gh->unread_count > 0 && gh->current_first > (gh->first_article + 1))
  929. X        ans1 = 'u';
  930. X    else if (gh->current_first <= gh->first_db_article)
  931. X        ans1 = 's';     /* no more articles to read */
  932. X    }
  933. X
  934. X    prompt("\1Number of%s articles\1 (%sua%ssne)  (%c) ",
  935. X       gh == orig_group ? " extra" : "",
  936. X       (gh->unread_count > 0) ? "j" : "",
  937. X       (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "",
  938. X       ans1);
  939. X
  940. X    if (novice)
  941. X    msg("Use: j)ump u)nread a)ll @)archive s)ubject n)ame e)ither or number");
  942. X
  943. X    answer = get_s(NONE, NONE, " jua@s=ne", NULL_FCT);
  944. X    if (answer == NULL) goto_return(ME_NO_REDRAW);
  945. X    if (*answer == NUL || *answer == SP) answer = &ans1;
  946. X
  947. X    switch (*answer) {
  948. X     case '@':
  949. X    if ((gh->master_flag & M_AUTO_ARCHIVE) == 0) {
  950. X        msg("No archive");
  951. X        goto_return(ME_NO_REDRAW);
  952. X    }
  953. X    answer = gh->archive_file;
  954. X    goto get_folder;
  955. X
  956. X     case 'u':
  957. X    access_mode |= ACC_ORIG_NEWSRC;
  958. X    first = gh->first_article + 1;
  959. X    goto enter_new_level;
  960. X
  961. X     case 'j':
  962. X    if (gh == orig_group || gh->unread_count <= 0) {
  963. X        msg("Cannot jump - no unread articles");
  964. X        goto_return(ME_NO_REDRAW);
  965. X    }
  966. X    jump_to_group = gh;
  967. X    goto_return(ME_QUIT);
  968. X
  969. X     case 'a':
  970. X    first = 0;
  971. X    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  972. X    goto more_articles;
  973. X
  974. X     case '=':
  975. X     case 's':
  976. X     case 'n':
  977. X     case 'e':
  978. X    goto get_mask;
  979. X
  980. X     case '0':
  981. X     case '1':
  982. X     case '2':
  983. X     case '3':
  984. X     case '4':
  985. X     case '5':
  986. X     case '6':
  987. X     case '7':
  988. X     case '8':
  989. X     case '9':
  990. X    first = gh->current_first - atol(answer);
  991. X    goto more_articles;
  992. X
  993. X     default:
  994. X    ding();
  995. X    goto_return(ME_NO_REDRAW);
  996. X    }
  997. X
  998. X
  999. X more_articles:
  1000. X    if (first > gh->last_db_article) goto_return(ME_NO_REDRAW);
  1001. X
  1002. X    if (gh != orig_group) goto enter_new_level;
  1003. X
  1004. X    if (gh->current_first <= gh->first_db_article) {
  1005. X    msg("No extra articles");
  1006. X    goto_return(ME_NO_REDRAW);
  1007. X    }
  1008. X
  1009. X    if (access_group(gh, first, gh->current_first == gh->first_article + 1 ?
  1010. X             gh->last_db_article : gh->current_first - 1,
  1011. X             ACC_EXTRA_ARTICLES | ACC_ALSO_CROSS_POSTINGS |
  1012. X             ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES,
  1013. X             (char *)NULL) < 0) {
  1014. X    msg("Cannot read extra articles (now)");
  1015. X    goto_return(ME_NO_REDRAW);
  1016. X    }
  1017. X
  1018. X    gh->current_first = first;
  1019. X    goto_return(ME_REDRAW);
  1020. X        
  1021. X get_folder:
  1022. X    m_endinput();
  1023. X    if (strcmp(answer, "+") == 0)
  1024. X    answer = (gh->save_file != NULL) ? gh->save_file : default_save_file;
  1025. X    if (!expand_file_name(buffer, answer, 1)) goto_return (ME_NO_REDRAW);
  1026. X    menu_cmd = folder_menu(buffer);
  1027. X    gh = NULL;
  1028. X    goto goto_exit;
  1029. X
  1030. X get_mask:
  1031. X    first = 0;
  1032. X    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  1033. X    switch (*answer) {
  1034. X     case '=':
  1035. X    *answer = 's';
  1036. X     case 's':
  1037. X    access_mode |= ACC_ON_SUBJECT;
  1038. X    break;
  1039. X     case 'n':
  1040. X    access_mode |= ACC_ON_SENDER;
  1041. X    break;
  1042. X     case 'e':
  1043. X    access_mode |= ACC_ON_SUBJECT | ACC_ON_SENDER;
  1044. X    break;
  1045. X    }
  1046. X    
  1047. X    if (get_from_macro || answer[1] == '=') {
  1048. X    mask = answer + 1;
  1049. X    if (*mask == '=') mask++;
  1050. X    } else {
  1051. X    prompt("%c=", *answer);
  1052. X    mask = get_s(ah == NULL ? NONE :
  1053. X             (access_mode & ACC_ON_SUBJECT ? ah->subject : ah->sender),
  1054. X             NONE, ah ? NONE : "%=", NULL_FCT);
  1055. X    if (mask == NULL) goto_return(ME_NO_REDRAW);
  1056. X    if (*mask == '%' || *mask == '=') {
  1057. X        if ((ah = get_menu_article()) == 0) goto_return(ME_NO_REDRAW);
  1058. X        *mask = NUL;
  1059. X    }
  1060. X    }
  1061. X
  1062. X    if (*mask == NUL) {
  1063. X    if (ah == NULL) goto_return(ME_NO_REDRAW);
  1064. X    strncpy(mask, (access_mode & ACC_ON_SUBJECT) ? ah->subject : ah->sender, GET_S_BUFFER);
  1065. X    mask[GET_S_BUFFER-1] = NUL;
  1066. X    }
  1067. X
  1068. X    if (*mask) {
  1069. X    if (case_fold_search) fold_string(mask);
  1070. X    } else
  1071. X    mask = NULL;
  1072. X
  1073. X enter_new_level:
  1074. X    mark_memory(&mem_marker);
  1075. X    m_endinput();
  1076. X    menu_cmd = group_menu(gh, first, access_mode, mask, menu);
  1077. X    release_memory(&mem_marker);
  1078. X
  1079. Xgoto_exit:
  1080. X    if (gh != orig_group)
  1081. X    if (orig_group) init_group(orig_group);
  1082. X
  1083. X    m_endinput();
  1084. X    return menu_cmd;
  1085. X}
  1086. X
  1087. Xstatic merged_header()
  1088. X{
  1089. X    so_printxy(0, 0, "MERGED NEWS GROUPS:  %d ARTICLES", n_articles);
  1090. X    clrline();
  1091. X
  1092. X    return 1;
  1093. X}
  1094. X
  1095. Xmerge_and_read(access_mode, mask)
  1096. Xflag_type access_mode;
  1097. Xchar *mask;
  1098. X{
  1099. X    register group_header *gh;
  1100. X    group_header dummy_group;
  1101. X    time_t t1, t2;
  1102. X    long kb = 0, kbleft = 0;
  1103. X
  1104. X    time(&t1); t2 = 0;
  1105. X
  1106. X    free_memory();
  1107. X
  1108. X    access_mode |= ACC_DONT_SORT_ARTICLES | ACC_MERGED_MENU;
  1109. X    if (!seq_cross_filtering)
  1110. X    if (also_read_articles || mask || also_cross_postings)
  1111. X        access_mode |= ACC_ALSO_CROSS_POSTINGS;
  1112. X    if (dont_split_digests)
  1113. X    access_mode |= ACC_DONT_SPLIT_DIGESTS;
  1114. X
  1115. X    Loop_Groups_Sequence(gh) {
  1116. X    if (gh->group_flag & G_FOLDER) continue;
  1117. X    if (gh->master_flag & M_NO_DIRECTORY) continue;
  1118. X    if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
  1119. X        continue;
  1120. X    if (!also_read_articles && gh->last_article >= gh->last_db_article)
  1121. X        continue;
  1122. X
  1123. X    kbleft += gh->data_write_offset;
  1124. X    }
  1125. X
  1126. X    Loop_Groups_Sequence(gh) {
  1127. X    if (s_hangup || s_keyboard) break;
  1128. X
  1129. X    if (gh->group_flag & G_FOLDER) {
  1130. X        printf("\n\rIgnoring folder: %s\n\r", gh->group_name);
  1131. X        continue;
  1132. X    }
  1133. X
  1134. X    if (gh->master_flag & M_NO_DIRECTORY) continue;
  1135. X    if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
  1136. X        continue;
  1137. X
  1138. X    if (also_read_articles)
  1139. X        access_mode |= ACC_ALSO_READ_ARTICLES;
  1140. X    else
  1141. X        if (gh->last_article >= gh->last_db_article)
  1142. X        continue;
  1143. X
  1144. X    if (init_group(gh) <= 0) continue;
  1145. X
  1146. X    if (t2 > 2 && kb > 50000) {
  1147. X        printf("\r%4lds\t%lds\t%s", kbleft/(kb/t2), (long)t2, gh->group_name);
  1148. X    } else
  1149. X#ifdef KBYTE_PER_SECOND
  1150. X        printf("\r%4lds\t%lds\t%s", kbleft/(KBYTE_PER_SECOND*1024)),
  1151. X        (long)t2, gh->group_name);
  1152. X#else
  1153. X        printf("\r\t%lds\t%s", (long)t2, gh->group_name);
  1154. X#endif
  1155. X    clrline();
  1156. X
  1157. X    access_group(gh, (article_number)(-1), gh->last_db_article,
  1158. X             access_mode, mask);
  1159. X
  1160. X    time(&t2); t2 -= t1;
  1161. X    kb += gh->data_write_offset;
  1162. X    kbleft -= gh->data_write_offset;
  1163. X    }
  1164. X    merge_memory();
  1165. X    if (n_articles == 0) return;
  1166. X    if (!dont_sort_articles) sort_articles(-1);
  1167. X
  1168. X    dummy_group.group_flag = G_FAKED;
  1169. X    dummy_group.master_flag = 0;
  1170. X    dummy_group.save_file = NULL;
  1171. X    dummy_group.group_name = "dummy";
  1172. X    dummy_group.kill_list = NULL;
  1173. X
  1174. X    current_group = &dummy_group;
  1175. X
  1176. X    kb = (kb + 1023) >> 10;
  1177. X    sprintf(delayed_msg, "Read %ld articles in %ld seconds (%ld kbyte/s)",
  1178. X        (long)db_read_counter, (long)t2, t2 > 0 ? kb/t2 : kb);
  1179. X
  1180. X    menu(merged_header);
  1181. X
  1182. X    free_memory();
  1183. X}
  1184. X
  1185. Xunsubscribe(gh)
  1186. Xgroup_header *gh;
  1187. X{
  1188. X    if (no_update) {
  1189. X    msg("nn started in \"no update\" mode");
  1190. X    return 0;
  1191. X    }
  1192. X
  1193. X    if (gh->group_flag & G_FOLDER) {
  1194. X    msg("cannot unsubscribe to a folder");
  1195. X    return 0;
  1196. X    }
  1197. X
  1198. X    if (gh->group_flag & G_UNSUBSCRIBED) {
  1199. X    prompt("Already unsubscribed.  \1Resubscribe to\1 %s ? ",
  1200. X           gh->group_name);
  1201. X    if (yes(0) <= 0) return 0;
  1202. X
  1203. X    add_to_newsrc(gh);
  1204. X    add_unread(gh, 1);
  1205. X    } else {
  1206. X    prompt("\1Unsubscribe to\1 %s ? ", gh->group_name);
  1207. X    if (yes(0) <= 0) return 0;
  1208. X
  1209. X    add_unread(gh, -1);
  1210. X    update_rc_all(gh, 1);
  1211. X    }
  1212. X
  1213. X    return 1;
  1214. X}
  1215. X
  1216. Xstatic disp_group(gh)
  1217. Xgroup_header *gh;
  1218. X{
  1219. X    if (pg_next() < 0) return -1;
  1220. X
  1221. X    printf("%c%6ld%c%s%s%s",
  1222. X       (gh->group_flag & G_MERGED) == 0 ? ' ' :
  1223. X       (gh->group_flag & G_MERGE_HEAD) ? '&' : '+',
  1224. X
  1225. X       (long)(gh->unread_count),
  1226. X
  1227. X       (gh == current_group) ? '*' : ' ',
  1228. X
  1229. X       gh->group_name,
  1230. X
  1231. X       (gh->group_flag & G_NEW) ? " NEW" :
  1232. X       (gh->group_flag & G_UNSUBSCRIBED) ? " (!)" : "",
  1233. X
  1234. X       gh->enter_macro ? " %" : "");
  1235. X
  1236. X    return 0;
  1237. X}
  1238. X
  1239. X/*
  1240. X * amount interpretation:
  1241. X *    -1    presentation sequence, unread,subscribed+current
  1242. X *     0    unread,subscribed
  1243. X *    1    unread,all
  1244. X *    2    all,all 3=>unsub
  1245. X */
  1246. X
  1247. Xgroup_overview(amount)
  1248. Xint amount;
  1249. X{
  1250. X    register group_header *gh;
  1251. X
  1252. X    clrdisp();
  1253. X
  1254. X    pg_init(0, 2);
  1255. X
  1256. X    if (amount < 0) {
  1257. X    Loop_Groups_Sequence(gh) {
  1258. X        if (gh->group_flag & G_FAKED) continue;
  1259. X        if (gh->master_flag & M_NO_DIRECTORY) continue;
  1260. X        if (gh != current_group)
  1261. X        if ((gh->group_flag & G_COUNTED) == 0) continue;
  1262. X        if (disp_group(gh) < 0) break;
  1263. X    }
  1264. X    } else
  1265. X    Loop_Groups_Sorted(gh) {
  1266. X    if (gh->master_flag & (M_NO_DIRECTORY | M_IGNORE_GROUP)) continue;
  1267. X    if (amount <= 1 && gh->unread_count <= 0) continue;
  1268. X    if (amount == 0 && (gh->group_flag & G_UNSUBSCRIBED)) continue;
  1269. X    if (amount == 3 && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
  1270. X
  1271. X    if (disp_group(gh) < 0) break;
  1272. X    }
  1273. X
  1274. X    pg_end();
  1275. X}
  1276. END_OF_FILE
  1277.   if test 22393 -ne `wc -c <'group.c'`; then
  1278.     echo shar: \"'group.c'\" unpacked with wrong size!
  1279.   fi
  1280.   # end of 'group.c'
  1281. fi
  1282. if test -f 'nntp.c' -a "${1}" != "-c" ; then 
  1283.   echo shar: Will not clobber existing file \"'nntp.c'\"
  1284. else
  1285.   echo shar: Extracting \"'nntp.c'\" \(22676 characters\)
  1286.   sed "s/^X//" >'nntp.c' <<'END_OF_FILE'
  1287. X/*
  1288. X * nntp module for nn.
  1289. X *
  1290. X * The original taken from the nntp 1.5 clientlib.c
  1291. X * Modified heavily for nn.
  1292. X *
  1293. X * Rene' Seindal (seindal@diku.dk) Thu Dec  1 18:41:23 1988
  1294. X *
  1295. X * I have modified Rene's code quite a lot for 6.4 -- I hope he
  1296. X * can still recognize a bit here and a byte there; in any case,
  1297. X * any mistakes are mine :-)  ++Kim
  1298. X */
  1299. X
  1300. X
  1301. X#include "config.h"
  1302. X
  1303. X/*
  1304. X *     nn maintains a cache of recently used articles to improve efficiency.
  1305. X *     To change the size of the cache, define NNTPCACHE in config.h to be
  1306. X *    the new size of this cache.
  1307. X */
  1308. X
  1309. X#ifndef NNTPCACHE
  1310. X#define NNTPCACHE    10
  1311. X#endif
  1312. X
  1313. X#ifdef NNTP
  1314. X#include <stdio.h>
  1315. X#include "nntp.h"
  1316. X#include <sys/socket.h>
  1317. X#include <netdb.h>
  1318. X
  1319. X/* This is necessary due to the definitions in m-XXX.h */
  1320. X#if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER)
  1321. X#include <netinet/in.h>
  1322. X#endif
  1323. X
  1324. Ximport char *db_directory, *tmp_directory, *news_active;
  1325. X
  1326. Xexport char nntp_server[256];    /* name of nntp server */
  1327. Xexport int use_nntp = 0;    /* bool: t iff we use nntp */
  1328. Xexport int nntp_failed = 0;    /* bool: t iff connection is broken in
  1329. X                   nntp_get_article() or nntp_get_active() */
  1330. X
  1331. Xexport nntp_cache_size = NNTPCACHE;
  1332. Xexport char *nntp_cache_dir = NULL;
  1333. X
  1334. Xexport int nntp_local_server = 0;
  1335. Xexport int nntp_debug = 0;
  1336. X
  1337. Ximport int silent, no_update;
  1338. X
  1339. Ximport int errno, sys_nerr;
  1340. Ximport char *sys_errlist[];
  1341. Xextern int user_error();
  1342. Xextern int sys_error();
  1343. X
  1344. X#define syserr() (errno >= 0 && errno < sys_nerr ? \
  1345. X          sys_errlist[errno] : "Unknown error.")
  1346. X
  1347. Ximport char *mktemp();
  1348. X
  1349. Xstatic FILE *nntp_in = NULL;        /* fp for reading from server */
  1350. Xstatic FILE *nntp_out = NULL;        /* fp for writing to server */
  1351. Xstatic int is_connected = 0;        /* bool: t iff we are connected */
  1352. Xstatic group_header *group_hd;        /* ptr to servers current group */
  1353. Xstatic int group_is_set = 0;            /* bool: t iff group_hd is set */
  1354. Xstatic int try_again = 0;        /* bool: t if timeout forces retry */
  1355. Xstatic int can_post = 0;        /* bool: t iff NNTP server accepts postings */
  1356. X
  1357. X#define ERR_TIMEOUT    503        /* Response code for timeout */
  1358. X
  1359. X/*
  1360. X * debug_msg: print a debug message.
  1361. X *
  1362. X *    The master appends prefix and str to a log file, and clients
  1363. X *    prints it as a message.
  1364. X *
  1365. X *    This is controlled via the nntp-debug variable in nn, and
  1366. X *    the option -D2 (or -D3 if the normal -D option should also
  1367. X *    be turned on).  Debug output from the master is written in
  1368. X *    $TMP/nnmaster.log.
  1369. X */
  1370. X
  1371. Xstatic debug_msg(prefix, str)
  1372. Xchar *prefix, *str;
  1373. X{
  1374. X    static FILE *f = NULL;
  1375. X    
  1376. X    if (who_am_i == I_AM_MASTER) {
  1377. X    if (f == NULL) {
  1378. X        f = open_file(relative(tmp_directory, "nnmaster.log"), OPEN_CREATE);
  1379. X        if (f == NULL) {
  1380. X        nntp_debug = 0;
  1381. X        return;
  1382. X        }
  1383. X    }        
  1384. X    fprintf(f, "%s %s\n", prefix, str);
  1385. X    fflush(f);
  1386. X    return;
  1387. X    }
  1388. X
  1389. X    msg("NNTP%s %s", prefix, str);
  1390. X    user_delay(1);
  1391. X}
  1392. X
  1393. X/*
  1394. X * io_error: signal an I/O error in talking to the server.
  1395. X *
  1396. X *     An nn client terminates a session with the user.  The master
  1397. X *    simply closes the connection.  The flag nntp_failed is set, for
  1398. X *    use by the master to terminate collection.
  1399. X *
  1400. X *    BUG: if the nntp server is forcibly killed, errno can contain a
  1401. X *    bogus value, resulting in strange error messages.  It is
  1402. X *    probably better just to write out the numerical value of errno.
  1403. X */
  1404. X
  1405. Xstatic io_error()
  1406. X{
  1407. X    if (who_am_i != I_AM_MASTER) {
  1408. X    user_error("Lost connection to NNTP server %s: %s", nntp_server, syserr());
  1409. X        /* NOTREACHED */
  1410. X    }
  1411. X    nntp_failed = 1;
  1412. X    if (is_connected) {
  1413. X    log_entry('N', "Lost connection to server %s: %s", nntp_server, syserr());
  1414. X    nntp_close_server();
  1415. X    }
  1416. X}
  1417. X
  1418. X/*
  1419. X * find_server: Find out which host to use as NNTP server.
  1420. X *
  1421. X *     This is done by consulting the file NNTP_SERVER (defined in
  1422. X *     config.h).  Set nntp_server[] to the host's name.
  1423. X */
  1424. X
  1425. Xstatic void find_server()
  1426. X{
  1427. X    char *cp, *name, *getenv();
  1428. X    char buf[BUFSIZ];
  1429. X    FILE *fp;
  1430. X
  1431. X    /*
  1432. X     * This feature cannot normally be enabled, because the database and
  1433. X     * the users rc file contains references to articles by number, and
  1434. X     * these numbers are not unique across NNTP servers.
  1435. X     */
  1436. X#ifdef DEBUG
  1437. X    if ((cp = getenv("NNTPSERVER")) != NULL) {
  1438. X    strncpy(nntp_server, cp, sizeof nntp_server);
  1439. X    return;
  1440. X    }
  1441. X#endif /* DEBUG */
  1442. X
  1443. X    name = NNTP_SERVER;
  1444. X    if (*name != '/')
  1445. X    name = relative(lib_directory, name);
  1446. X
  1447. X    if ((fp = open_file(name, OPEN_READ)) != NULL) {
  1448. X    while (fgets(buf, sizeof buf, fp) != 0) {
  1449. X        if (*buf == '#' || *buf == '\n')
  1450. X        continue;
  1451. X        if ((cp = strchr(buf, '\n')) != 0)
  1452. X        *cp = '\0';
  1453. X        strncpy(nntp_server, buf, sizeof nntp_server);
  1454. X        fclose(fp);
  1455. X        return;
  1456. X    }
  1457. X    fclose(fp);
  1458. X    }
  1459. X
  1460. X    if (who_am_i != I_AM_MASTER)
  1461. X    printf("\nCannot find name of NNTP server.\nCheck %s\n", name);
  1462. X
  1463. X    sys_error("Failed to find name of NNTP server!");
  1464. X}
  1465. X
  1466. X/*
  1467. X * get_server_line: get a line from the server.
  1468. X *
  1469. X *     Expects to be connected to the server.
  1470. X *     The line can be any kind of line, i.e., either response or text.
  1471. X */
  1472. X
  1473. Xstatic get_server_line(string, size)
  1474. X    char *string;
  1475. X    int size;
  1476. X{
  1477. X    register char *cp, *nl;
  1478. X
  1479. X    if (fgets(string, size, nntp_in) == NULL) {
  1480. X    io_error();
  1481. X    return -1;
  1482. X    }
  1483. X    for (cp = string, nl = NULL; *cp != NUL; cp++) {
  1484. X    if (*cp == CR) {
  1485. X        nl = cp;
  1486. X        break;
  1487. X    }
  1488. X    if (nl == NULL && *cp == NL)
  1489. X        nl = cp;
  1490. X    }
  1491. X    if (nl != NULL) *nl = NUL;
  1492. X
  1493. X    return 0;
  1494. X}
  1495. X
  1496. X/*
  1497. X * get_server: get a response line from the server.
  1498. X *
  1499. X *     Expects to be connected to the server.
  1500. X *     Returns the numerical value of the reponse, or -1 in case of errors.
  1501. X */
  1502. X
  1503. Xstatic get_server(string, size)
  1504. X    char *string;
  1505. X    int size;
  1506. X{
  1507. X    if (get_server_line(string, size) < 0)
  1508. X    return -1;
  1509. X
  1510. X    if (nntp_debug) debug_msg("<<<", string);
  1511. X
  1512. X    return isdigit(*string) ? atoi(string) : 0;
  1513. X}
  1514. X
  1515. X/*
  1516. X * get_socket:  get a connection to the nntp server.
  1517. X *
  1518. X *     Doesn't return in case of errors.
  1519. X */
  1520. X
  1521. Xstatic get_socket()
  1522. X{
  1523. X    int s;
  1524. X    struct sockaddr_in sin;
  1525. X    struct servent *getservbyname(), *sp;
  1526. X    struct hostent *gethostbyname(), *hp;
  1527. X#ifdef h_addr
  1528. X    int     x = 0;
  1529. X    register char **cp;
  1530. X#endif
  1531. X
  1532. X    if ((sp = getservbyname("nntp", "tcp")) ==  NULL)
  1533. X    sys_error("nntp/tcp: Unknown service.\n");
  1534. X
  1535. X    if ((hp = gethostbyname(nntp_server)) == NULL)
  1536. X    sys_error("NNTP server %s unknown.\n", nntp_server);
  1537. X
  1538. X    bzero((char *) &sin, sizeof(sin));
  1539. X    sin.sin_family = hp->h_addrtype;
  1540. X    sin.sin_port = sp->s_port;
  1541. X
  1542. X#ifdef h_addr
  1543. X    /* get a socket and initiate connection -- use multiple addresses */
  1544. X
  1545. X    for (cp = hp->h_addr_list; cp && *cp; cp++) {
  1546. X    s = socket(hp->h_addrtype, SOCK_STREAM, 0);
  1547. X    if (s < 0)
  1548. X        sys_error("Can't get NNTP socket: %s\n", syserr());
  1549. X    bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
  1550. X
  1551. X    x = connect(s, (struct sockaddr *)&sin, sizeof (sin));
  1552. X    if (x == 0)
  1553. X        break;
  1554. X    if (who_am_i != I_AM_MASTER)
  1555. X        msg("Connecting to %s failed: %s", nntp_server, syserr());
  1556. X    (void) close(s);
  1557. X    s = -1;
  1558. X    }
  1559. X    if (x < 0 && who_am_i != I_AM_MASTER)
  1560. X    user_error("Giving up on NNTP server %s!\n", nntp_server);
  1561. X#else                    /* no name server */
  1562. X    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  1563. X    sys_error("Can't get NNTP socket: %s\n", syserr());
  1564. X
  1565. X    /* And then connect */
  1566. X    bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
  1567. X    if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
  1568. X    if (who_am_i == I_AM_MASTER)
  1569. X        sys_error("Connecting to %s failed: %s", nntp_server, syserr());
  1570. X    s = -1;
  1571. X    }
  1572. X#endif
  1573. X    return s;
  1574. X}
  1575. X
  1576. X/*
  1577. X * connect_server: initialise a connection to the nntp server.
  1578. X *
  1579. X *     It expects nntp_server[] to be set previously, by a call to
  1580. X *     nntp_check.  It is called from nntp_get_article() and
  1581. X *    nntp_get_active() if there is no established connection.
  1582. X */
  1583. X
  1584. Xstatic connect_server()
  1585. X{
  1586. X    int sockt_rd, sockt_wr;
  1587. X    int response;
  1588. X    char line[NNTP_STRLEN];
  1589. X    
  1590. X    if (who_am_i != I_AM_MASTER && !silent)
  1591. X    msg("Connecting to NNTP server %s ... ", nntp_server);
  1592. X
  1593. X    nntp_failed = 1;
  1594. X    is_connected = 0;
  1595. X
  1596. X    sockt_rd = get_socket();
  1597. X    if (sockt_rd < 0)
  1598. X    return -1;
  1599. X
  1600. X    if ((nntp_in = fdopen(sockt_rd, "r")) == NULL) {
  1601. X    close(sockt_rd);
  1602. X        return -1;
  1603. X    }
  1604. X    sockt_wr = dup(sockt_rd);
  1605. X    if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
  1606. X    close(sockt_wr);
  1607. X    fclose(nntp_in);
  1608. X        nntp_in = NULL;               /* from above */
  1609. X        return -1;
  1610. X    }
  1611. X
  1612. X    /* Now get the server's signon message */
  1613. X    response = get_server(line, sizeof(line));
  1614. X
  1615. X    if (who_am_i == I_AM_MASTER) {
  1616. X    if (response != OK_CANPOST && response != OK_NOPOST) {
  1617. X        log_entry('N', "Failed to connect to NNTP server");
  1618. X        log_entry('N', "Response: %s", line);
  1619. X        fclose(nntp_out);
  1620. X        fclose(nntp_in);
  1621. X        return -1;
  1622. X    }
  1623. X    } else {
  1624. X    switch (response) {
  1625. X    case OK_CANPOST:
  1626. X        can_post = 1;
  1627. X        break;
  1628. X    case OK_NOPOST:
  1629. X        can_post = 0;
  1630. X        break;
  1631. X    default:
  1632. X        user_error(line);
  1633. X        /* NOTREACHED */
  1634. X    }
  1635. X    }
  1636. X    if (who_am_i != I_AM_MASTER && !silent)
  1637. X    msg("Connecting to NNTP server %s ... ok (%s)",
  1638. X        nntp_server, can_post ? "posting is allowed" : "no posting");
  1639. X
  1640. X    is_connected = 1;
  1641. X    group_is_set = 0;
  1642. X    nntp_failed = 0;
  1643. X    try_again = 0;
  1644. X    return 0;
  1645. X}
  1646. X
  1647. X
  1648. X/*
  1649. X * put_server:  send a line to the nntp server.
  1650. X *
  1651. X *     Expects to be connected to the server.
  1652. X */
  1653. X
  1654. Xstatic put_server(string)
  1655. X    char *string;
  1656. X{
  1657. X    if (nntp_debug) debug_msg(">>>", string);
  1658. X
  1659. X    fprintf(nntp_out, "%s\r\n", string);
  1660. X    if (fflush(nntp_out) == EOF) {
  1661. X    io_error();
  1662. X    return -1;
  1663. X    }
  1664. X    return 0;
  1665. X}
  1666. X
  1667. X/*
  1668. X * ask_server:  ask the server a question and return the answer.
  1669. X *
  1670. X *    Expects to be connected to the server.
  1671. X *    Returns the numerical value of the reponse, or -1 in case of
  1672. X *    errors.
  1673. X *    Contains some code to handle server timeouts intelligently.
  1674. X */
  1675. X
  1676. X/*VARARGS*/
  1677. Xstatic ask_server(va_alist)
  1678. Xva_dcl
  1679. X{
  1680. X    char buf[NNTP_STRLEN];
  1681. X    char *fmt;
  1682. X    int response;
  1683. X    use_vararg;
  1684. X
  1685. X    start_vararg;
  1686. X    fmt = va_arg1(char *);
  1687. X    vsprintf(buf, fmt, va_args2toN);
  1688. X    end_vararg;
  1689. X
  1690. X    if (put_server(buf) < 0)
  1691. X    return -1;
  1692. X    response = get_server(buf, sizeof(buf));
  1693. X
  1694. X    /*
  1695. X     * Handle the response from the server.  Responses are handled as
  1696. X     * followes:
  1697. X     *
  1698. X     * 100-199    Informational.  Passed back. (should they be ignored?).
  1699. X     * 200-299    Ok messages.  Passed back.
  1700. X     * 300-399    Ok and proceed.  Can not happen in nn.
  1701. X     * 400-499    Errors (no article, etc).  Passed up and handled there.
  1702. X     * 500-599    Fatal NNTP errors.  Handled below.
  1703. X     */
  1704. X    if (response == ERR_GOODBYE || response > ERR_COMMAND) {
  1705. X    nntp_failed = 1;
  1706. X    nntp_close_server();
  1707. X
  1708. X    if (response != ERR_TIMEOUT) {    /* if not timeout, complain */
  1709. X        sys_error("NNTP %s response: %d", buf, response);
  1710. X        /* NOTREACHED */
  1711. X    }
  1712. X    try_again = 1;
  1713. X    group_is_set = 0;
  1714. X    }
  1715. X    return response;
  1716. X}
  1717. X
  1718. X/*
  1719. X * copy_text: copy text response into file.
  1720. X *
  1721. X *     Copies a text response into an open file.
  1722. X *    Return -1 on error, 0 otherwise.  It is treated as an error, if
  1723. X *    the returned response it not what was expected.
  1724. X */
  1725. X
  1726. Xstatic int last_copy_blank;
  1727. X
  1728. Xstatic copy_text(fp)
  1729. Xregister FILE *fp;
  1730. X{
  1731. X    char buf[NNTP_STRLEN];
  1732. X    register char *cp;
  1733. X    register int nlines;
  1734. X
  1735. X    nlines = 0;
  1736. X    last_copy_blank = 0;
  1737. X    while (get_server_line(buf, sizeof buf) >= 0) {
  1738. X    cp = buf;
  1739. X    if (*cp == '.')
  1740. X        if (*++cp == '\0') {
  1741. X        if (nlines <= 0) break;
  1742. X        if (nntp_debug) {
  1743. X            sprintf(buf, "%d lines", nlines);
  1744. X            debug_msg("COPY", buf);
  1745. X        }
  1746. X        return 0;
  1747. X        }
  1748. X    fputs(cp, fp);
  1749. X    last_copy_blank = (*cp == NUL);
  1750. X    putc('\n', fp);
  1751. X    nlines++;
  1752. X    }
  1753. X    fclose(fp);
  1754. X    if (nntp_debug) debug_msg("COPY", "EMPTY");
  1755. X    return -1;
  1756. X}
  1757. X
  1758. X
  1759. Xstatic do_set_group()
  1760. X{
  1761. X    int n;
  1762. X
  1763. X    switch (n = ask_server("GROUP %s", group_hd->group_name)) {
  1764. X     case OK_GROUP:
  1765. X    group_is_set = 1;
  1766. X    return 1;
  1767. X
  1768. X     case ERR_NOGROUP:
  1769. X    log_entry('N', "NNTP: group %s not found", group_hd->group_name);
  1770. X    return -1;
  1771. X
  1772. X     default:
  1773. X    if (try_again) return 0;    /* Handle nntp server timeouts */
  1774. X    break;
  1775. X    }
  1776. X    if (!nntp_failed) {
  1777. X    log_entry('N', "GROUP %s response: %d", group_hd->group_name, n);
  1778. X    nntp_failed = 1;
  1779. X    }
  1780. X    return -1;
  1781. X}
  1782. X
  1783. X/*
  1784. X * The following functions implements a simple lru cache of recently
  1785. X * accessed articles.  It is a simple way to improve effeciency.  Files
  1786. X * must be kept by name, because the rest of the code expects to be able
  1787. X * to open an article multiple times, and get separate file pointers.
  1788. X */
  1789. X
  1790. Xstruct cache {
  1791. X    char        *file_name;    /* file name */
  1792. X    article_number    art;        /* article stored in file */
  1793. X    group_header    *grp;        /* from this group */
  1794. X    unsigned        time;        /* time last accessed */
  1795. X} cache[NNTPCACHE];
  1796. X
  1797. Xstatic unsigned time_counter = 1;        /* virtual time */
  1798. X
  1799. X/*
  1800. X * search_cache: search the cache for an (article, group) pair.
  1801. X *
  1802. X *     Returns a pointer to the slot where it is, null otherwise
  1803. X */
  1804. X
  1805. Xstatic struct cache *search_cache(art, gh)
  1806. X    article_number art;
  1807. X    group_header *gh;
  1808. X{
  1809. X    struct cache *cptr = cache;
  1810. X    int i;
  1811. X
  1812. X    if (who_am_i == I_AM_MASTER) return NULL;
  1813. X
  1814. X    if (nntp_cache_size > NNTPCACHE) nntp_cache_size = NNTPCACHE;
  1815. X
  1816. X    for (i = 0; i < nntp_cache_size; i++, cptr++)
  1817. X    if (cptr->art == art && cptr->grp == gh) {
  1818. X        cptr->time = time_counter++;
  1819. X        return cptr;
  1820. X    }
  1821. X    return NULL;
  1822. X}
  1823. X
  1824. X/*
  1825. X * new_cache_slot: get a free cache slot.
  1826. X *
  1827. X *     Returns a pointer to the allocated slot.
  1828. X *     Frees the old filename, and allocates a new, unused filename.
  1829. X *    Cache files can also stored in a common directory defined in
  1830. X *    ~/.nn or CACHE_DIRECTORY if defined in config.h.
  1831. X */
  1832. X
  1833. Xstatic struct cache *new_cache_slot()
  1834. X{
  1835. X    register struct cache *cptr = cache;
  1836. X    int i, lru;
  1837. X    unsigned min_time = time_counter;
  1838. X    char name[FILENAME];
  1839. X
  1840. X    if (nntp_cache_dir == NULL) {
  1841. X#ifdef CACHE_DIRECTORY
  1842. X    nntp_cache_dir = CACHE_DIRECTORY;
  1843. X#else
  1844. X    if (who_am_i == I_AM_MASTER)
  1845. X        nntp_cache_dir = db_directory;
  1846. X    else
  1847. X        nntp_cache_dir = nn_directory;
  1848. X#endif
  1849. X    }
  1850. X
  1851. X    if (who_am_i == I_AM_MASTER) {
  1852. X    cptr = &cache[0];
  1853. X    if (cptr->file_name == NULL)
  1854. X        cptr->file_name = mk_file_name(nntp_cache_dir, "master_cache");
  1855. X    return cptr;
  1856. X    }
  1857. X
  1858. X    for (i = 0; i < nntp_cache_size; i++, cptr++)
  1859. X    if (min_time > cptr->time) {
  1860. X        min_time = cptr->time;
  1861. X        lru = i;
  1862. X    }
  1863. X    cptr = &cache[lru];
  1864. X
  1865. X    if (cptr->file_name == NULL) {
  1866. X    sprintf(name, "%s/nn-%d.%02d~", nntp_cache_dir, process_id, lru);
  1867. X    cptr->file_name = copy_str(name);
  1868. X    } else
  1869. X    unlink(cptr->file_name);
  1870. X
  1871. X    cptr->time = time_counter++;
  1872. X    return cptr;
  1873. X}
  1874. X
  1875. X/*
  1876. X * clean_cache: clean up the cache.
  1877. X *
  1878. X *     Removes all allocated files.
  1879. X */
  1880. X
  1881. Xstatic void clean_cache()
  1882. X{
  1883. X    struct cache *cptr = cache;
  1884. X    int i;
  1885. X
  1886. X    for (i = 0; i < nntp_cache_size; i++, cptr++)
  1887. X    if (cptr->file_name)
  1888. X        unlink(cptr->file_name);
  1889. X}
  1890. X
  1891. X/*
  1892. X * nntp_check: Find out whether we need to use NNTP.
  1893. X *
  1894. X *     This is done by comparing the NNTP servers name with whatever
  1895. X *     gethostname() returns.
  1896. X *    use_nntp and news_active are initialised as a side effect.
  1897. X */
  1898. X
  1899. Xnntp_check()
  1900. X{
  1901. X    char host[128];
  1902. X
  1903. X    if (nntp_local_server) return;
  1904. X
  1905. X    find_server();
  1906. X    gethostname(host, sizeof host);
  1907. X    use_nntp = strcmp(host, nntp_server) != 0; /* too simplistic ??? */
  1908. X
  1909. X    if (use_nntp) {
  1910. X    freeobj(news_active);
  1911. X    news_active = mk_file_name(db_directory, "ACTIVE");
  1912. X    }
  1913. X}
  1914. X
  1915. X/*
  1916. X * nntp_no_post: Check to see whether posting is allowed.
  1917. X */
  1918. X
  1919. Xnntp_no_post()
  1920. X{
  1921. X    if (!is_connected && connect_server() < 0)
  1922. X    return 1;            /* If we cannot connect, neither can inews */
  1923. X    if (can_post == 0) {
  1924. X    msg("NNTP server does not allow postings from this host.  Sorry!");
  1925. X    return 1;
  1926. X    }
  1927. X    return 0;
  1928. X}
  1929. X
  1930. X
  1931. X/*
  1932. X * nntp_set_group: set the server's current group.
  1933. X *
  1934. X *     Actual communication is delayed until an article is accessed, to
  1935. X *     avoid unnecessary traffic.
  1936. X */
  1937. X
  1938. Xnntp_set_group(gh)
  1939. X    group_header *gh;
  1940. X{
  1941. X    group_hd = gh;
  1942. X    group_is_set = 0;
  1943. X    return 0;
  1944. X}
  1945. X
  1946. X/*
  1947. X * nntp_get_active:  get a copy of the active file.
  1948. X *
  1949. X *     If we are the master get a copy of the file from the nntp server.
  1950. X *     nnadmin just uses the one we already got.  In this way the master
  1951. X *    can maintain a remote copy of the servers active file.
  1952. X *    We try to be a little smart, if not inefficient, about the
  1953. X *    modification times on the local active file.
  1954. X *    Even when the master is running on the nntp server, a separate
  1955. X *    copy of the active file will be made for access via NFS.
  1956. X */
  1957. X
  1958. Xnntp_get_active()
  1959. X{
  1960. X    FILE *old, *new;
  1961. X    char bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
  1962. X    char *new_name;
  1963. X    int same, n;
  1964. X
  1965. X    if (who_am_i != I_AM_MASTER)
  1966. X    return access(news_active, 4);
  1967. X
  1968. X again:
  1969. X    if (!is_connected && connect_server() < 0)
  1970. X    return -1;
  1971. X
  1972. X    new_name = mktemp(relative(db_directory, ".actXXXXXX"));
  1973. X
  1974. X    switch (n = ask_server("LIST")) {
  1975. X     case OK_GROUPS:
  1976. X    new = open_file(new_name, OPEN_CREATE_RW|MUST_EXIST);
  1977. X    if (copy_text(new) == 0) break;
  1978. X    unlink(new_name);
  1979. X    if (!nntp_failed) {
  1980. X        log_entry('N', "LIST empty");
  1981. X        nntp_failed = 1;
  1982. X    }
  1983. X    return -1;
  1984. X     default:
  1985. X    if (try_again) goto again; /* Handle nntp server timeouts */
  1986. X    log_entry('N', "LIST response: %d", n);
  1987. X    return -1;
  1988. X    }
  1989. X
  1990. X    rewind(new);
  1991. X    same = 0;
  1992. X    if ((old = open_file(news_active, OPEN_READ)) != NULL) {
  1993. X    do {
  1994. X        fgets(bufo, sizeof bufo, old);
  1995. X        fgets(bufn, sizeof bufn, new);
  1996. X    } while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
  1997. X    same = feof(old) && feof(new);
  1998. X    fclose(old);
  1999. X    }
  2000. X    fclose(new);
  2001. X
  2002. X    if (same)
  2003. X    unlink(new_name);
  2004. X    else
  2005. X    if (rename(new_name, news_active) != 0)
  2006. X        sys_error("Cannot rename %s to %s", new_name, news_active);
  2007. X
  2008. X    return 0;
  2009. X}
  2010. X
  2011. X/*
  2012. X * nntp_get_article_list: get list of all article numbers in group
  2013. X *
  2014. X *     Sends XHDR command to the server, and parses the following
  2015. X *    text response to get a list of article numbers which is saved
  2016. X *    in a list and returned.
  2017. X *    Return NULL on error.  It is treated as an error, if
  2018. X *    the returned response it not what was expected.
  2019. X */
  2020. X
  2021. Xstatic article_number *article_list = NULL;
  2022. Xstatic long art_list_length = 0;
  2023. X
  2024. Xstatic sort_art_list(f1, f2)
  2025. Xregister article_number *f1, *f2;
  2026. X{
  2027. X    return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
  2028. X}
  2029. X
  2030. Xarticle_number *nntp_get_article_list(gh)
  2031. Xgroup_header *gh;
  2032. X{
  2033. X    char buf[NNTP_STRLEN];
  2034. X    register article_number *art;
  2035. X    register char *cp;
  2036. X    register long count = 0;    /* No. of completions plus one */
  2037. X    int n;
  2038. X    static int try_listgroup = 1;
  2039. X
  2040. X again:
  2041. X    if (!is_connected && connect_server() < 0)
  2042. X    return NULL;
  2043. X
  2044. X    /* it is really an extreme waste of time to use XHDR since all we    */
  2045. X    /* are interested in is the article numbers (as we do locally).    */
  2046. X    /* If somebody hacks up an nntp server that understands LISTGROUP    */
  2047. X    /* they will get much less load on the nntp server            */
  2048. X    /* It should simply return the existing article numbers is the group*/
  2049. X    /* -- they don't even have to be sorted (only XHDR needs that)    */
  2050. X
  2051. X    if (try_listgroup) {
  2052. X    switch (n = ask_server("LISTGROUP %s", group_hd->group_name)) {
  2053. X     case OK_GROUP:
  2054. X        break;
  2055. X     default:
  2056. X        if (try_again) goto again; /* Handle nntp server timeouts */
  2057. X        log_entry('N', "LISTGROUP response: %d", n);
  2058. X     case ERR_COMMAND:
  2059. X        try_listgroup = 0;
  2060. X        goto again;    /* error may have closed down server connection */
  2061. X    }
  2062. X    }
  2063. X    if (!try_listgroup) {
  2064. X    if (group_is_set == 0)
  2065. X        switch (do_set_group()) {
  2066. X         case -1:
  2067. X        return NULL;
  2068. X         case 0:
  2069. X        goto again;
  2070. X         case 1:
  2071. X        break;
  2072. X        }
  2073. X
  2074. X    switch (n = ask_server("XHDR message-id %ld-%ld",
  2075. X        (long)gh->first_db_article, (long)gh->last_db_article)) {
  2076. X     case OK_HEAD:
  2077. X        break;
  2078. X     default:
  2079. X        if (try_again) goto again; /* Handle nntp server timeouts */
  2080. X        log_entry('N', "XHDR response: %d", n);
  2081. X     case ERR_COMMAND:
  2082. X        nntp_failed = 2;
  2083. X        return NULL;
  2084. X    }
  2085. X    }
  2086. X
  2087. X    count = 0;
  2088. X    art = article_list;
  2089. X    
  2090. X    while (get_server_line(buf, sizeof buf) >= 0) {
  2091. X    cp = buf;
  2092. X    if (*cp == '.' && *++cp == '\0') break;
  2093. X
  2094. X    if (count == art_list_length) {
  2095. X        art_list_length += 250;
  2096. X        article_list = resizeobj(article_list, article_number, art_list_length + 1);
  2097. X        art = article_list + count;
  2098. X    }
  2099. X    *art++ = atol(cp);
  2100. X    count++;
  2101. X    }
  2102. X    *art = 0;
  2103. X
  2104. X    if (try_listgroup)
  2105. X    quicksort(article_list, count, article_number, sort_art_list);
  2106. X    *art = 0;
  2107. X    return article_list;
  2108. X}
  2109. X
  2110. X/*
  2111. X * nntp_get_article: get an article from the server.
  2112. X *
  2113. X *     Returns a FILE pointer.
  2114. X *    If necessary the server's current group is set.
  2115. X *    The article (header and body) are copied into a file, so they
  2116. X *    are seekable (nn likes that).
  2117. X */
  2118. X
  2119. Xstatic char *mode_cmd[] = {
  2120. X    "ARTICLE",
  2121. X    "HEAD",
  2122. X    "BODY"
  2123. X};
  2124. X
  2125. XFILE *nntp_get_article(article, mode)
  2126. Xarticle_number article;
  2127. Xint mode;    /* 0 => whole article, 1 => head only, 2 => body only */
  2128. X{
  2129. X    FILE *tmp;
  2130. X    static struct cache *cptr;
  2131. X    int n;
  2132. X    
  2133. X again:
  2134. X    if (!is_connected && connect_server() < 0) {
  2135. X    return NULL;
  2136. X    }
  2137. X
  2138. X    /*
  2139. X     * Set the server group to the current group
  2140. X     */
  2141. X    if (group_is_set == 0)
  2142. X    switch (do_set_group()) {
  2143. X     case -1:
  2144. X        return NULL;
  2145. X     case 0:
  2146. X        goto again;
  2147. X     case 1:
  2148. X        break;
  2149. X    }
  2150. X
  2151. X    /*
  2152. X     * Search the cache for the requested article, and allocate a new
  2153. X     * slot if necessary (if appending body, we already got it).
  2154. X     */
  2155. X
  2156. X    if (mode != 2) {
  2157. X    cptr = search_cache(article, group_hd);
  2158. X    if (cptr != NULL) goto out;
  2159. X    cptr = new_cache_slot();
  2160. X    }
  2161. X    
  2162. X    /*
  2163. X     * Copy the article.
  2164. X     */
  2165. X    switch (n = ask_server("%s %ld", mode_cmd[mode], (long)article)) {
  2166. X     case OK_ARTICLE:
  2167. X     case OK_HEAD:
  2168. X    tmp = open_file(cptr->file_name, OPEN_CREATE|MUST_EXIST);
  2169. X    if (copy_text(tmp) < 0)
  2170. X        return NULL;
  2171. X
  2172. X    if (mode == 1 && !last_copy_blank)
  2173. X        fputc(NL, tmp); /* add blank line after header */
  2174. X
  2175. X    cptr->art = article;
  2176. X    cptr->grp = group_hd;
  2177. X    fclose(tmp);
  2178. X    goto out;
  2179. X
  2180. X     case OK_BODY:
  2181. X    tmp = open_file(cptr->file_name, OPEN_APPEND|MUST_EXIST);
  2182. X    fseek(tmp, (off_t)0, 2);
  2183. X    if (copy_text(tmp) < 0)
  2184. X        return NULL;
  2185. X    fclose(tmp);
  2186. X    goto out;
  2187. X    
  2188. X     case ERR_NOARTIG:
  2189. X    return NULL;
  2190. X
  2191. X     default:
  2192. X    if (try_again) goto again; /* Handle nntp server timeouts */
  2193. X    log_entry('N', "ARTICLE %ld response: %d", (long)article, n);
  2194. X    nntp_failed = 1;
  2195. X    return NULL;
  2196. X    }
  2197. X
  2198. X out:
  2199. X    return open_file(cptr->file_name, OPEN_READ|MUST_EXIST);
  2200. X}
  2201. X
  2202. X/*
  2203. X *    Return local file name holding article
  2204. X */
  2205. X
  2206. Xchar *nntp_get_filename(art, gh)
  2207. Xarticle_number art;
  2208. Xgroup_header *gh;
  2209. X{
  2210. X    struct cache *cptr;
  2211. X
  2212. X    cptr = search_cache(art, gh);
  2213. X
  2214. X    return cptr == NULL ? NULL : cptr->file_name;
  2215. X}
  2216. X
  2217. X/*
  2218. X * nntp_close_server: close the connection to the server.
  2219. X */
  2220. X
  2221. Xnntp_close_server()
  2222. X{
  2223. X    if (!is_connected)
  2224. X    return;
  2225. X
  2226. X    if (!nntp_failed) {            /* avoid infinite recursion */
  2227. X    int n;
  2228. X
  2229. X    n = ask_server("QUIT");
  2230. X    if (n != OK_GOODBYE)
  2231. X        ;                /* WHAT NOW ??? */
  2232. X    }
  2233. X
  2234. X    (void) fclose(nntp_out);
  2235. X    (void) fclose(nntp_in);
  2236. X
  2237. X    is_connected = 0;
  2238. X}
  2239. X
  2240. X/*
  2241. X * nntp_cleanup:  clean up after an nntp session.
  2242. X *
  2243. X *    Called from nn_exit().
  2244. X */
  2245. X
  2246. Xnntp_cleanup()
  2247. X{
  2248. X    if (is_connected)
  2249. X    nntp_close_server();
  2250. X    clean_cache();
  2251. X}
  2252. X#endif /* NNTP */
  2253. X
  2254. END_OF_FILE
  2255.   if test 22676 -ne `wc -c <'nntp.c'`; then
  2256.     echo shar: \"'nntp.c'\" unpacked with wrong size!
  2257.   fi
  2258.   # end of 'nntp.c'
  2259. fi
  2260. echo shar: End of archive 10 \(of 22\).
  2261. cp /dev/null ark10isdone
  2262. MISSING=""
  2263. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ; do
  2264.     if test ! -f ark${I}isdone ; then
  2265.     MISSING="${MISSING} ${I}"
  2266.     fi
  2267. done
  2268. if test "${MISSING}" = "" ; then
  2269.     echo You have unpacked all 22 archives.
  2270.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  2271. else
  2272.     echo You still must unpack the following archives:
  2273.     echo "        " ${MISSING}
  2274. fi
  2275. exit 0
  2276.  
  2277. exit 0 # Just in case...
  2278.